package com.iwuyc.tools.commons.classtools;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.iwuyc.tools.commons.exception.MethodNotFoundException;
import com.iwuyc.tools.commons.structs.basic.Pair;
import com.iwuyc.tools.commons.structs.collections.NavigateTree;
import com.iwuyc.tools.commons.structs.collections.NavigateTreeNode;
import com.iwuyc.tools.commons.util.collection.ArrayUtil;
import lombok.extern.slf4j.Slf4j;

import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * 类对象的工具类。
 *
 * @author Neil
 * @since 2021.4
 */
@Slf4j
public abstract class ClassUtils {
    /**
     * 基础类型 跟 包装类型 的映射关系。
     */
    public static final Map<Class<?>, Class<?>> PACKAGE_TYPE_BY_PRIMITIVE_TYPE;
    public static final Map<Class<?>, Class<?>> PRIMITIVE_TYPE_BY_PACKAGE_TYPE;
    private static final Cache<TypeFunction<?, ?>, String> CACHE_LAMBDA_INFO = CacheBuilder.newBuilder().build();

    static {

        Map<Class<?>, Class<?>> temp = new HashMap<>();
        temp.put(void.class, Void.class);

        temp.put(byte.class, Byte.class);
        temp.put(short.class, Short.class);
        temp.put(int.class, Integer.class);
        temp.put(long.class, Long.class);

        temp.put(float.class, Float.class);
        temp.put(double.class, Double.class);

        temp.put(boolean.class, Boolean.class);
        temp.put(char.class, Character.class);

        PACKAGE_TYPE_BY_PRIMITIVE_TYPE = Collections.unmodifiableMap(temp);

        final Map<Class<?>, Class<?>> primitiveTypeByPackageType = new HashMap<>();
        for (Map.Entry<Class<?>, Class<?>> item : temp.entrySet()) {
            primitiveTypeByPackageType.put(item.getValue(), item.getKey());
        }
        PRIMITIVE_TYPE_BY_PACKAGE_TYPE = Collections.unmodifiableMap(primitiveTypeByPackageType);

    }

    private ClassUtils() {
    }

    /**
     * 获取class类对象，不做类的初始化。以屏蔽讨厌的try……catch块。
     *
     * @param classPath 类的名字
     * @return 一个 {@link Optional} 对象，如果成功加载，则返回相应的对象，否则返回一个 {@link Optional#empty()}
     */
    public static Optional<Class<?>> loadClass(String classPath) {
        return loadClass(classPath, null);
    }

    /**
     * 获取class类对象，不做类的初始化。以屏蔽讨厌的try……catch块。
     *
     * @param classPath 类路径
     * @param loader    类加载器
     * @return 类对象
     */
    public static Optional<Class<?>> loadClass(String classPath, ClassLoader loader) {
        return loadClass(classPath, false, loader);
    }

    /**
     * 获取class类对象，不做类的初始化。以屏蔽讨厌的try……catch块。
     *
     * @param classPath    类路径
     * @param isInitialize 是否对类进行初始化
     * @param loader       类加载器
     * @return 类对象
     */
    public static Optional<Class<?>> loadClass(String classPath, boolean isInitialize, ClassLoader loader) {
        if (null == loader) {
            loader = ClassUtils.class.getClassLoader();
        }
        return AccessController.doPrivileged(new ClassLoadPrivilegedAction(classPath, isInitialize, loader));
    }

    /**
     * 根据类名实例化一个对象。
     *
     * @param targetClass 返回的目标类型。
     * @param clazzName   类名。
     * @param args        构造函数的参数。
     * @param <I>         目标类型
     * @return 实例化后的对象。
     */
    public static <I> I instance(Class<I> targetClass, String clazzName, Object... args) {
        Optional<Class<?>> clazzOpt = loadClass(clazzName);
        return clazzOpt.map(aClass -> instance(targetClass, aClass, args)).orElse(null);
    }

    /**
     * 根据类对象实例化一个对象
     *
     * @param targetClass 返回的目标类型
     * @param clazz       类对象
     * @param args        构造函数的参数
     * @param <I>         目标类型
     * @return 实例化后的对象
     * @author Neil
     */
    public static <I> I instance(Class<I> targetClass, Class<?> clazz, Object... args) {
        return AccessController.doPrivileged(new InstancePrivilegedAction<>(targetClass, clazz, args));
    }

    /**
     * 根据类对象实例化一个对象
     *
     * @param clazz 类对象
     * @param args  构造函数的参数
     * @param <T>   目标类型
     * @return 实例化后的对象
     * @author Neil
     */
    public static <T> T instance(Class<T> clazz, Object... args) {
        return instance(clazz, clazz, args);
    }

    /**
     * 比较两个类型是否相同，主要是解决基础类型跟包装类型不一致的情况，如果不存在基础类型跟包装类型同时存在的比较，不建议使用该方法。
     *
     * @param firstType    第一个类型
     * @param superClazz   父类
     * @param isAssignable 是否依据继承关系进行判断。如果为true，则判断another是否为firstType的父类。
     * @return 如果是同一种类型，则返回true，否则返回false;
     * @author Neil
     */
    public static boolean compareType(Class<?> firstType, Class<?> superClazz, boolean isAssignable) {
        if (null == firstType || null == superClazz) {
            return firstType == superClazz;
        }

        if (firstType.isPrimitive()) {
            firstType = PACKAGE_TYPE_BY_PRIMITIVE_TYPE.get(firstType);
            return firstType != null && firstType.equals(superClazz);
        } else if (superClazz.isPrimitive()) {
            superClazz = PACKAGE_TYPE_BY_PRIMITIVE_TYPE.get(superClazz);
            return superClazz != null && superClazz.equals(firstType);
        }
        if (isAssignable) {
            return superClazz.isAssignableFrom(firstType);
        }
        return firstType.equals(superClazz);
    }

    /**
     * 将类的父类、接口转换为一棵树
     *
     * @param rootType 根类型
     * @return 转换后的对象
     */
    private static NavigateTree<Class<?>, Integer> superClassTree(Class<?> rootType) {

        final NavigateTree<Class<?>, Integer> classTree = new NavigateTree<>();
        classTree.add(null, rootType, 0);
        final Class<?> innerFirstType;
        if (PACKAGE_TYPE_BY_PRIMITIVE_TYPE.containsKey(rootType)) {
            final Class<?> packageType = PACKAGE_TYPE_BY_PRIMITIVE_TYPE.get(rootType);
            classTree.add(null, packageType, 0);
            innerFirstType = packageType;
        } else if (PRIMITIVE_TYPE_BY_PACKAGE_TYPE.containsKey(rootType)) {
            final Class<?> primitiveType = PRIMITIVE_TYPE_BY_PACKAGE_TYPE.get(rootType);
            classTree.add(null, primitiveType, 0);
            innerFirstType = rootType;
        } else {
            innerFirstType = rootType;
        }

        final Deque<Pair<Class<?>, Integer>> classDeque = new ArrayDeque<>();
        classDeque.add(Pair.serializable(innerFirstType, 0));

        while (!classDeque.isEmpty()) {
            final Pair<Class<?>, Integer> item = classDeque.pop();
            final Class<?> clazz = item.getKey();

            final Integer treeLevel = item.getVal();
            final Integer nexTreeLevel = treeLevel + 1;

            final Class<?> superclass = clazz.getSuperclass();
            if (superclass != null) {
                classTree.add(clazz, superclass, nexTreeLevel);
                classDeque.add(Pair.serializable(superclass, nexTreeLevel));
            }
            for (Class<?> superInterface : clazz.getInterfaces()) {
                classTree.add(clazz, superInterface, nexTreeLevel);
                classDeque.add(Pair.serializable(superInterface, nexTreeLevel));
            }

        }

        return classTree;
    }

    /**
     * 调用对象的方法，静态方法的话，请传入 Class 对象，而无需传入实例对象。
     *
     * @param instance   实例或者Class对象
     * @param methodName 方法名
     * @param parameters 方法的入参
     * @return 执行结果
     * @throws MethodNotFoundException 方法如果不存在，则会抛出该异常
     * @author Neil
     */
    public static Object callMethod(Object instance, String methodName, Object... parameters) throws MethodNotFoundException {
        return callMethod0(instance, methodName, false, parameters);
    }

    /**
     * 忽略访问修饰符强制调用对象的方法，静态方法的话，请传入 Class 对象，而无需传入实例对象。
     *
     * @param instance   实例或者Class对象
     * @param methodName 方法名
     * @param parameters 方法的入参
     * @return 执行结果
     * @throws MethodNotFoundException 方法如果不存在，则会抛出该异常
     * @author Neil
     */
    public static Object mandatoryCallMethod(Object instance, String methodName, Object... parameters) throws MethodNotFoundException {
        return callMethod0(instance, methodName, true, parameters);
    }

    @SuppressWarnings("squid:S3011")
    private static Object callMethod0(Object instance, String methodName, boolean declared, Object... parameters) {

        Class<?> instanceType;
        if (instance instanceof Class) {
            instanceType = (Class<?>) instance;
        } else {
            instanceType = instance.getClass();
        }
        Class<?>[] parameterTypeList = typeList(parameters);
        final Optional<Method> methodOpt = chooseBestMethod(instanceType, methodName, parameterTypeList, declared);
        if (!methodOpt.isPresent()) {
            log.warn("Can't found any method for name:[{}];Parameter Type List:{}", methodName, Arrays.toString(parameterTypeList));
            throw new MethodNotFoundException("Can't found any method for name:" + methodName);
        }

        Method method = methodOpt.get();

        if (!method.isAccessible()) {
            method.setAccessible(true);
        }

        try {
            return method.invoke(instance, parameters);
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            log.error("Call [{}] raise an error.Cause:[{}]", methodName, e.getMessage());
        }
        return null;

    }

    /**
     * 获取对象的类型列表，将列表中的对象的类型，按照输入的顺序进行输出。
     *
     * @param object 待获取的数据列表
     * @return 类型列表
     * @author Neil
     */
    public static Class<?>[] typeList(Object... object) {
        int parametersLength = ArrayUtil.arrayLength(object);

        Class<?>[] objectTypeList = new Class<?>[parametersLength];
        for (int i = 0; i < parametersLength; i++) {
            objectTypeList[i] = object[i].getClass();
        }
        return objectTypeList;
    }

    /**
     * 根据参数列表，从executable列表中获取最匹配的方法。
     *
     * @param executables    executable 列表
     * @param parameterTypes 参数类型列表
     * @return 参数列表最匹配的executable对象
     * @author Neil
     */
    private static <E extends Executable> E chooseBestMatchExecutable(Collection<E> executables, Class<?>[] parameterTypes) {

        final NavigateTree<Class<?>, Integer>[] parameterClassSupperTree = turnToTree(parameterTypes);

        int maxScope = Integer.MAX_VALUE;
        E bestMatch = null;
        for (E executable : executables) {

            final int scope = compareTypeList(executable.getParameterTypes(), parameterClassSupperTree);
            if (scope < maxScope) {
                bestMatch = executable;
                maxScope = scope;
            }
        }
        return bestMatch;

    }

    private static int compareTypeList(Class<?>[] parameterTypes, NavigateTree<Class<?>, Integer>[] parameterClassSupperTree) {
        if (ArrayUtil.arrayLength(parameterTypes) != ArrayUtil.arrayLength(parameterClassSupperTree)) {
            return Integer.MAX_VALUE;
        }
        int result = 0;
        for (int i = 0; i < parameterTypes.length; i++) {
            final NavigateTree<Class<?>, Integer> superClassType = parameterClassSupperTree[i];
            final Class<?> parameterType = parameterTypes[i];
            final Optional<NavigateTreeNode<Class<?>, Integer>> targetType = superClassType.get(parameterType);
            if (!targetType.isPresent()) {
                return Integer.MAX_VALUE;
            }
            result += targetType.map(NavigateTreeNode::getVal).orElse(0);
        }
        return result;
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private static NavigateTree<Class<?>, Integer>[] turnToTree(Class<?>[] parameterTypes) {
        final NavigateTree[] result = new NavigateTree[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            result[i] = superClassTree(parameterTypes[i]);
        }
        return result;
    }

    /**
     * 获取lambda方法的方法名
     *
     * @param function 函数
     * @param <I>      入参
     * @param <O>      返回值
     * @return 方法名
     */
    @SuppressWarnings("squid:S3011")
    public static <I, O> String getLambdaMethodName(TypeFunction<I, O> function) {
        try {
            return CACHE_LAMBDA_INFO.get(function, () -> {
                Optional<Method> writeReplaceMethodOpt = findWriteReplaceMethod(function);

                if (!writeReplaceMethodOpt.isPresent()) {
                    return "";
                }

                final Method writeReplaceMethod = writeReplaceMethodOpt.get();
                if (!writeReplaceMethod.isAccessible()) {
                    writeReplaceMethod.setAccessible(true);
                }
                Object replacement = writeReplaceMethod.invoke(function);
                if (!(replacement instanceof SerializedLambda)) {
                    return "";
                }
                SerializedLambda lambdaSerialized = (SerializedLambda) replacement;
                return lambdaSerialized.getImplMethodName();

            });
        } catch (Exception e) {
            // do nothing
            throw new IllegalStateException("获取方法名失败。", e);
        }
    }

    private static <I, O> Optional<Method> findWriteReplaceMethod(TypeFunction<I, O> function) {
        for (Class<?> clazz = function.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                if ("writeReplace".equals(method.getName())) {
                    return Optional.of(method);
                }
            }

        }
        return Optional.empty();
    }

    /**
     * 查询最优的函数
     *
     * @param targetClazz    目标类
     * @param methodName     函数名
     * @param parameterTypes 参数
     * @param declared       是否查询非public访问修饰符修饰的构造函数，true表示查询所有的，false表示只查询public的构造函数
     * @param <T>            实际类型
     * @return 构造函数
     */
    public static <T> Optional<Method> chooseBestMethod(Class<T> targetClazz, String methodName, Class<?>[] parameterTypes, boolean declared) {
        MethodPrivilegedAction action = new MethodPrivilegedAction(targetClazz, methodName, parameterTypes, declared);
        return AccessController.doPrivileged(action);
    }

    /**
     * 查询最优的构造函数
     *
     * @param targetClazz    目标类
     * @param parameterTypes 参数
     * @param declared       是否查询非public访问修饰符修饰的构造函数，true表示查询所有的，false表示只查询public的构造函数
     * @param <T>            实际类型
     * @return 构造函数
     */
    @SuppressWarnings("unchecked")
    public static <T> Optional<Constructor<T>> chooseBestContractor(Class<T> targetClazz, Class<?>[] parameterTypes, boolean declared) {
        try {
            if (ArrayUtil.isEmpty(parameterTypes)) {
                return Optional.of(declared ? targetClazz.getDeclaredConstructor() : targetClazz.getConstructor());
            }
            final Constructor<?>[] constructorsArr = declared ? targetClazz.getDeclaredConstructors() : targetClazz.getConstructors();
            final Collection<Constructor<?>> constructors = new ArrayList<>(constructorsArr.length);
            constructors.addAll(Arrays.asList(constructorsArr));
            return Optional.ofNullable((Constructor<T>) chooseBestMatchExecutable(constructors, parameterTypes));

        } catch (NoSuchMethodException e) {
            log.warn("获取方法异常，原因是：", e);
            return Optional.empty();
        }
    }

    private static class InstancePrivilegedAction<S, I> implements PrivilegedAction<I> {

        private final Class<I> targetClass;
        private final Class<S> clazz;
        private final Object[] args;

        public InstancePrivilegedAction(Class<I> targetClass, Class<S> clazz, Object[] args) {
            this.targetClass = targetClass;
            this.clazz = clazz;
            this.args = args;
        }

        @SuppressWarnings({"unchecked", "squid:S3011"})
        @Override
        public I run() {
            try {
                if (!targetClass.isAssignableFrom(clazz)) {
                    return null;
                }
                Constructor<?> constructor = getConstructor();
                if (!constructor.isAccessible()) {
                    log.debug("The constructor can't visit.Set it true for accessible.");
                    constructor.setAccessible(true);
                }
                Object i = constructor.newInstance(args);
                return (I) i;
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                log.error("error:", e);
                log.error("Can't init class[{}]", clazz);
            }
            return null;
        }

        private Constructor<S> getConstructor() {
            Class<?>[] parameterTypes = new Class<?>[args.length];
            for (int i = 0; i < parameterTypes.length; i++) {
                parameterTypes[i] = args[i].getClass();
            }

            final Optional<Constructor<S>> constructor = chooseBestContractor(this.clazz, parameterTypes, true);
            return constructor.orElseThrow(() -> new MethodNotFoundException("未找到合适的构造函数"));
        }

    }

    private static class ClassLoadPrivilegedAction implements PrivilegedAction<Optional<Class<?>>> {

        private final ClassLoader loader;
        private final String classPath;
        private final boolean isInitialize;

        public ClassLoadPrivilegedAction(String classPath, boolean isInitialize, ClassLoader loader) {
            this.classPath = classPath;
            this.isInitialize = isInitialize;
            this.loader = loader;
        }

        @Override
        public Optional<Class<?>> run() {

            Class<?> result = null;
            try {
                result = Class.forName(this.classPath, this.isInitialize, this.loader);
            } catch (ClassNotFoundException e) {
                log.error("Can't found class:[{}]", classPath);
            }
            return Optional.ofNullable(result);
        }
    }

    private static class MethodPrivilegedAction implements PrivilegedAction<Optional<Method>> {

        private final Class<?> targetClazz;
        private final String methodName;
        private final Class<?>[] parameterTypeList;
        private final boolean declared;

        public MethodPrivilegedAction(Class<?> targetClazz, String methodName, Class<?>[] parameterTypeList, boolean declared) {
            this.targetClazz = targetClazz;
            this.methodName = methodName;
            this.parameterTypeList = parameterTypeList;
            this.declared = declared;
        }

        @Override
        public Optional<Method> run() {
            Method[] declaredMethods = declared ? targetClazz.getDeclaredMethods() : targetClazz.getMethods();

            final Collection<Method> nameMatchMethod = new ArrayList<>(declaredMethods.length);
            for (Method method : declaredMethods) {
                if (!methodName.equals(method.getName())) {
                    continue;
                }
                nameMatchMethod.add(method);
            }
            Method bestMatchMethod = chooseBestMatchExecutable(nameMatchMethod, this.parameterTypeList);
            if (null == bestMatchMethod) {
                throw new MethodNotFoundException("未找到适合的方法：" + methodName);
            }
            return Optional.of(bestMatchMethod);
        }
    }
}
